第五章:将本地 MCP Server 连接到 ChatGPT,创建自己的 Health Tracker 插件
上一章,我们已经完成了 Health Tracker 的核心功能。
通过 MCP Inspector,我们可以调用 Python 编写的工具,将饮食、运动、体重和断食记录保存到 SQLite 数据库中。
但在实际使用过程中,我发现了一个问题。
每次记录饮食,都需要自己填写 JSON 参数,这与直接使用数据库管理工具有什么区别?
例如,记录一根香蕉,我们需要输入:
{
"record_date": "2026-10-20",
"meal_type": "breakfast",
"food_name": "香蕉",
"calories": 105,
"weight_g": 118,
"protein_g": 1.3,
"carbs_g": 27,
"fat_g": 0.4
}
这些参数不仅填写起来麻烦,而且还需要我们提前知道食物的营养成分。
而我真正希望的操作方式是:
今天早上吃了一根香蕉,帮我记录一下。
ChatGPT 应该自动理解用户的意思,识别食物、估算营养成分,然后调用我们编写的 record_food 工具。
这正是本章要实现的效果。
本章目标:
- 理解 ChatGPT 为什么无法直接访问本地 MCP Server。
- 使用 Tunnel 将本地 MCP 服务连接到 ChatGPT。
- 在 ChatGPT 中创建私人 Health Tracker 插件。
- 让 ChatGPT 发现并调用我们开发的 MCP 工具。
- 使用自然语言完成饮食、运动、体重和断食记录。
- 理解 ChatGPT、MCP Server 和 SQLite 之间的职责分工。
本章继续使用 Mac 作为开发环境,不需要购买云服务器,也不需要将 MCP Server 部署到公网。
5.1 为什么 MCP Inspector 可以连接,ChatGPT 却不行?
首先回顾一下上一章的 MCP Server 启动代码:
if __name__ == "__main__":
init_db()
mcp.run(
transport="streamable-http",
host="127.0.0.1",
port=8000,
)
其中:
host="127.0.0.1"
表示 MCP Server 只监听本机回环地址。
因此,我们可以在 Mac 上通过以下地址访问它:
http://127.0.0.1:8000/mcp
MCP Inspector 之所以能够连接,是因为它与 MCP Server 都运行在我们的本地开发环境中。
但是,ChatGPT 的服务并不运行在我们的 Mac 上。
如果把这个地址直接填写到 ChatGPT 插件配置中:
http://127.0.0.1:8000/mcp
ChatGPT 并不能因此访问我们的电脑。
原因在于:127.0.0.1 永远指向发起连接的设备自身,而不是我们的 Mac。
那么,应该怎么办?
我们需要在 ChatGPT 和本地 MCP Server 之间建立一条可以访问的通信通道。
这就引出了 Tunnel。
5.2 Tunnel 到底是什么?
Tunnel 中文通常称为隧道。
它的作用是让原本只能在本地访问的服务,通过一条受管理的连接,被外部客户端访问。
在我们的项目中,连接关系如下:

需要注意,Tunnel 并不是 MCP 协议本身。
我们之前开发的 MCP Server,仍然使用 Streamable HTTP 传输方式。
Tunnel 只是负责让 ChatGPT 能够访问这个 MCP 服务。
因此,不需要重新开发 MCP 工具,也不需要把 SQLite 数据库上传到 ChatGPT。
数据库仍然保存在自己的 Mac 上,ChatGPT 通过工具接口操作数据。
当然,这也意味着 Mac 必须保持运行。关闭电脑后,本地 MCP Server 和 Tunnel 都会中断。我们将在后续云端部署章节解决这个问题。
5.3 启动本地 Health Tracker MCP Server
在连接 ChatGPT 之前,先确认我们自己的 MCP Server 正常运行。
打开终端,进入项目目录:
cd health-tracker-agent
启动服务:
uv run python main.py
正常情况下,MCP Server 会监听:
http://127.0.0.1:8000/mcp
此时不要关闭终端。
如果 Python 程序停止,ChatGPT 就无法调用我们的工具。
先使用 Inspector 检查
建议在连接 ChatGPT 之前,先通过 MCP Inspector 验证以下工具:
| 工具名称 | 功能 |
|---|---|
record_food | 保存饮食 |
get_food_records | 查询饮食 |
get_daily_calories | 统计热量 |
record_exercise | 保存运动 |
record_weight | 保存体重 |
record_fasting | 保存断食 |
get_daily_summary | 查询综合健康记录 |
这些工具应当能够正常执行。

如果 Inspector 都无法调用,就应该先排查本地 Python 程序,而不是直接在AI智能体中寻找原因。
5.4 安装 OpenAI Tunnel Client
【特别说明】本节使用tunnle是为了本地测试,后面会使用云服务器鉴权的方式。如果没有chatgpt账号,本章后面内容可以先跳过。
我们需要在 Mac 上安装一个程序,让它负责连接 OpenAI 的 Tunnel 服务,并将请求转发给本地 MCP Server。
这个程序叫作 tunnel-client。
根据 OpenAI 的官方项目说明,在 macOS 上可以使用 Homebrew 安装。
第一步:安装 Tunnel Client
打开一个新的终端:
brew install openai/tools/tunnel-client
安装完成后,查看版本:
tunnel-client --version

再查看快速入门说明:
tunnel-client help quickstart

这里需要特别提醒:Tunnel Client 与 MCP SDK 是两个独立的软件。
MCP SDK 安装在 Python 项目中,负责运行 MCP Server;Tunnel Client 是单独运行的程序,负责转发请求。
因此,不需要通过 uv add 安装 Tunnel Client。
第二步:创建 Tunnel
打开 OpenAI Platform:

在 Tunnel 管理页面创建一条新的 Tunnel,例如命名为:
Health Tracker Tunnel

创建成功后,如下图

平台会提供一个 Tunnel ID,格式类似:
tunnel_0123456789abcdef0123456789abcdef
这个 ID 是示例,不是实际可用的 Tunnel。
另外,还需要创建具有适当权限的 Runtime API Key,用于 Tunnel Client 连接 OpenAI 服务。
点击左侧菜单“API keys”, 进入页面后,点击右上角“Create new secret key”,如下图:

填写表单,如下图:

Name填写:health-tracker-tunnel,
project选择:Default
Permissioins选择: All
点击“Create secret key”按钮创建,会显示API KEY, 保存好这个API(不要泄露),后面会使用它,如下图:

完成后,刷新页面如下图:

需要区分:
- Tunnel ID:标识我们创建的隧道。
- Runtime API Key:用于运行 Tunnel Client 的凭证。就是上面配置的API KEY.
- Admin API Key:仅在需要通过命令行管理 Tunnel 时使用,本教程不需要。
API Key 不要写进 Python 源代码,也不要上传到 Git 仓库。
第三步:配置本地 Tunnel
因为我们的 MCP Server 已经通过 HTTP 运行,所以选择 HTTP 连接方式,而不是让 Tunnel Client 再启动一个 Python 子进程。
注意:不同版本的 Tunnel Client 可能提供不同的示例配置名称。先执行:
tunnel-client profiles samples list

列表中有4种模式,我们选择sample_mcp_remote_no_auth, 在终端中执行以下命令,把示例 Tunnel ID 替换成自己的:
tunnel-client init \
--sample sample_mcp_remote_no_auth \
--profile health-tracker \
--tunnel-id <你的Tunnel-ID> \
--mcp-server-url http://127.0.0.1:8000/mcp
接下来,在当前终端会话中配置 Runtime API Key:
read -s CONTROL_PLANE_API_KEY

粘贴自己的 API Key 并按回车。输入内容不会回显。然后输入下面的命令:
export CONTROL_PLANE_API_KEY
这里没有将密钥直接写在命令参数中,能够减少它出现在终端历史中的风险。
注意:如果只是直接在终端执行
export,新开终端后通常需要重新设置。 它只对当前终端会话及其启动的程序有效,不会永久保存。
第四步:检查 Tunnel 配置
执行:
tunnel-client doctor \
--profile health-tracker \
--explain
这个命令会检查 Tunnel 配置、网络连接以及本地 MCP Server 的可达性。

如果检查失败,优先确认:
- Python MCP Server 是否仍然运行。
- Tunnel ID 是否正确。
- Runtime API Key 是否具有所需权限。
- MCP Server URL 是否为
http://127.0.0.1:8000/mcp。
第五步:运行 Tunnel
检查通过后,执行:
tunnel-client run --profile health-tracker

现在我们需要保持两个程序同时运行:
| 终端 | 运行程序 |
|---|---|
| 终端一 | uv run python main.py |
| 终端二 | tunnel-client run --profile health-tracker |
只有 MCP Server 和 Tunnel Client 都正常运行,ChatGPT 才能通过 Tunnel 调用工具。
【说明】:前面介绍的mcp server 这个工具是测试时使用,真正上线后,就不需要它了。
5.5 在 ChatGPT 中创建 Health Tracker 插件
接下来,我们进入 ChatGPT。
在当前的 Plugins 界面中,创建自定义 MCP Server 的操作流程是:
- 打开 Plugins(插件)。
- 点击加号,选择 Add custom MCP server(添加自定义 MCP 服务器)。
- 填写插件名称和描述。
- 在 Connection(连接方式)中选择 Tunnel。
- 选择刚才创建的 Tunnel,或者填写对应的 Tunnel ID。
- 配置身份验证,阅读并确认安全风险提示。
- 点击 Create as a plugin(创建为插件)。


注意:链接方式选择隧道,填写Tunnel ID,而不是直接访问本地 URL。
创建完成后,点击插件,选择个人,或者输入关键词搜索:health
接下来,就可以在你的AI中调用这个插件了。比如我在chatgpt手机端(当然也可以在codex中使用),输入@Health Tracker, 提问你有哪些工具,效果如下图所示。
如果后面修改了mcp server 代码,比如添加了新工具,但是使用插件时没有检测到,那么可以在插件管理界面,点击刷新工具,如下图所示。刷新完成,即可使用。

身份验证与安全
如果当前实验环境使用无需身份验证的本地 MCP Server,需要理解其中的风险:
- Tunnel 不等于数据库访问权限控制。
- 能使用该插件的主体可能触发数据写入或查询。
- 删除和修改工具尤其需要谨慎授权。
- 不要把测试插件随意发布给其他用户。
本课程先以个人私有插件进行开发测试。正式提供给其他用户时,需要设计独立的身份验证、用户隔离和权限控制。本节使用tunnle是为了本地测试,后面会使用云服务器鉴权的方式。
创建成功后,ChatGPT 会尝试发现 MCP Server 提供的工具。
5.6 ChatGPT 是怎么知道我们有哪些工具的?
创建插件时,我们并没有在 ChatGPT 中手动填写:
record_food
record_exercise
record_weight
record_fasting
get_daily_summary
但是,ChatGPT 却可以发现这些工具。
为什么?
因为 MCP 协议提供了工具发现机制。
它与上一章 MCP Inspector 的工作原理相同。
ChatGPT 可以通过 MCP 协议获取服务器提供的工具名称、描述和参数 Schema。
例如,我们在 Python 中编写:
@mcp.tool()
def record_weight(
record_date: str,
weight_kg: float,
notes: str | None = None,
) -> dict:
"""记录指定日期的体重,单位为公斤 kg。"""
ChatGPT 可以获得该工具的结构化描述,知道:
- 工具名称是
record_weight。 - 它用于记录体重。
record_date是日期字符串。weight_kg是公斤数。notes是可选备注。
所以,当用户说:
今天体重 170 斤,帮我记录。
ChatGPT 就有条件判断应该调用 record_weight,并把 170 斤换算成 85 公斤。
这也是为什么我们在编写 MCP 工具时,要认真设计函数名称、参数类型和 Docstring。
这些信息不仅是代码说明,也是 AI 理解工具用途的重要依据。
5.7 第一次使用自然语言调用 MCP 工具
现在,最令人期待的环节来了。
我们不再使用 MCP Inspector,而是直接在 ChatGPT 中测试 Health Tracker。
打开一个新的聊天,确认当前会话可以使用刚刚创建的 Health Tracker 插件。
测试一:记录体重
输入:
今天我的体重是 170 斤,帮我记录。
我们先不关心最终回答,而是观察 ChatGPT 是否调用了工具。
理想情况下,ChatGPT 会完成两个步骤。
第一步,理解用户的意思:
用户希望保存体重
170 斤 = 85 kg
第二步,生成工具调用参数:
{
"record_date": "2026-10-20",
"weight_kg": 85,
"notes": "用户报告体重为170斤"
}
其中日期应由 ChatGPT 根据当前日期和用户时区确定,这里只是课程测试日期。
随后,ChatGPT 调用:
record_weight
MCP Server 执行 SQLite 插入操作,返回记录 ID。
ChatGPT 再根据真实执行结果告诉用户是否保存成功。
注意:只有 MCP 工具实际返回保存成功,才能认定记录已经写入数据库。
如果 ChatGPT 只是回答“已记录”,但没有真正调用工具,那并不代表数据已经保存。
测试二:记录饮食
接下来输入:
今天早餐吃了一根香蕉,帮我估算热量和营养成分,并保存到 Health Tracker。
这次比记录体重更有意思。
因为我们没有告诉 ChatGPT 香蕉的热量,也没有提供蛋白质、碳水和脂肪的数据。
ChatGPT 需要自己完成营养估算,再调用 record_food。
例如,一根中等大小的香蕉,可以得到类似以下估算参数:
{
"record_date": "2026-10-20",
"meal_type": "breakfast",
"food_name": "香蕉",
"calories": 105,
"weight_g": 118,
"protein_g": 1.3,
"carbs_g": 27,
"fat_g": 0.4,
"notes": "中等大小香蕉一根,营养成分为估算值"
}
这些数据是估算值,不代表用户实际吃下的香蕉一定重 118 克。
我们开发的 Python 程序不需要知道香蕉是什么,也不需要内置营养成分数据库。
它只负责验证参数并保存。
测试三:查询数据库
现在输入:
查询今天的健康记录,告诉我吃了什么、摄入了多少热量,以及今天的体重。
ChatGPT 应该选择:
get_daily_summary
然后根据 SQLite 返回的数据生成回答。
这里有一个很重要的区别:
ChatGPT 可以估算营养,但查询历史记录时,应以 MCP Server 返回的数据库数据为准。
如果数据库中没有记录,就应该明确告诉用户没有查到,而不是根据之前的聊天内容编造一条记录。
5.8 为什么工具描述会影响 ChatGPT 的表现?
在刚才的测试中,我们可能遇到一个问题。
用户说:
今天吃了一根香蕉,帮我记录。
ChatGPT 成功调用了 record_food,却只保存了食物名称和热量,没有保存蛋白质、碳水和脂肪。
这是为什么?
回顾第三章的函数定义:
protein_g: float | None = Nonecarbs_g: float | None = Nonefat_g: float | None = None
这些参数都是可选的。
所以,ChatGPT 即使不填写它们,也符合工具的参数要求。
但我们的真实需求是:尽可能估算完整的营养数据。
这时,就需要改进工具描述。
例如,将 record_food 的 Docstring 修改为:
"""
记录用户摄入的食物。
当用户提供食物名称、数量或图片信息时,
尽可能根据已有营养知识估算以下数据:
- calories:热量,单位 kcal
- weight_g:可食部分重量,单位 g
- protein_g:蛋白质,单位 g
- carbs_g:碳水化合物,单位 g
- fat_g:脂肪,单位 g
估算结果应考虑食物份量和烹饪方式。
对于无法合理估算的数据,使用 None,
不要将未知值填写为 0。
在 notes 中注明估算依据和不确定性。
"""
这样,工具描述就不仅告诉 ChatGPT 如何调用函数,还说明了调用时应该尽量提供哪些信息。
不过,Docstring 只是指导,不保证模型每次都能准确估算。服务端仍然需要做好参数校验。
MCPServer 的 instructions
除了工具描述,我们还可以在创建 MCP Server 时提供服务器级别的说明:
mcp = MCPServer(
"Health Tracker",
instructions=(
"这是一个私人健康记录服务。"
"用户要求记录饮食时,尽可能估算食物的"
"热量、可食重量、蛋白质、碳水和脂肪。"
"营养数据为估算值时,应明确说明。"
"查询历史记录时,以工具返回的数据库数据为准。"
"修改或删除记录时,应准确确认目标记录。"
),
)
这类说明可以帮助支持服务器 instructions 的 MCP 客户端理解服务用途,但不同客户端对这些说明的利用方式可能不同。
因此,关键规则仍然应该同时体现在工具的名称、参数、描述以及服务端校验逻辑中。
5.9 修改 MCP 工具后,为什么 ChatGPT 看不到变化?
现在,我们修改了 record_food 的 Docstring。
重新启动 Python MCP Server 后,MCP Inspector 可能已经能够读取新的工具描述。
但 ChatGPT 仍然可能显示之前的版本。
这是因为 ChatGPT 对插件工具定义可能存在缓存。
因此,我们需要区分两件事情:
第一件事:重新启动 MCP Server。
让 Python 程序加载新的代码。
第二件事:刷新 ChatGPT 插件工具。
让 ChatGPT 重新发现服务器提供的工具定义。
在 ChatGPT 中找到 Health Tracker 插件,进入插件管理界面,使用:
管理应用 → 刷新工具
不同版本的界面名称可能有所不同。
什么情况下需要刷新?
| 修改内容 | 重启 MCP Server | 刷新 ChatGPT 工具 |
|---|---|---|
| 修改 SQL 查询逻辑 | 需要 | 通常不需要 |
| 修改 Python 函数内部逻辑 | 需要 | 通常不需要 |
| 新增 MCP 工具 | 需要 | 需要 |
| 删除 MCP 工具 | 需要 | 需要 |
| 修改工具参数 | 需要 | 需要 |
| 修改 Docstring | 需要 | 建议 |
| 修改 MCPServer instructions | 需要 | 建议 |
| SQLite 新增数据 | 不需要 | 不需要 |
通常,修改 Python 代码后不需要重新创建 Tunnel,也不需要重新创建整个 ChatGPT 插件。
只要 Tunnel Client 仍然正常运行,并且 MCP Server 的地址没有变化,就可以继续使用原来的连接。
5.10 常见问题与排查方法
问题一:ChatGPT 显示无法连接 Health Tracker
首先确认两个程序是否正常运行。
终端一:
uv run python main.py
终端二:
tunnel-client run --profile health-tracker
如果其中一个程序已经停止,就可能导致连接失败。
然后执行:
tunnel-client doctor \
--profile health-tracker \
--explain
根据诊断信息进一步排查。
问题二:Tunnel 正常,但 ChatGPT 没有发现工具
可以按照下面的顺序检查:
- MCP Inspector 是否能够正常列出工具。
- Tunnel 是否指向正确的本地 MCP URL。
- ChatGPT 插件是否关联了正确的 Tunnel ID。
- 当前账号或工作区是否允许使用自定义 MCP 工具。
- 是否需要刷新插件工具定义。
特别注意,Tunnel 连接成功并不等于 MCP 工具一定可用。
网络连接、MCP 协议交互和 ChatGPT 插件权限是不同的环节。
问题三:ChatGPT 不调用工具,直接回答
例如,用户说:
今天吃了一根香蕉。
ChatGPT 可能只回答香蕉的营养成分,并没有保存数据。
因为这句话没有明确表达保存意图。
测试阶段可以直接说:
使用 Health Tracker 的
record_food工具,把今天早餐吃的一根香蕉保存到数据库。
这样更容易判断问题究竟出在工具连接,还是模型对用户意图的理解。
等工具调用验证成功,再尝试更自然的表达方式。
问题四:为什么有些账号无法创建或使用写入工具?
ChatGPT 的自定义 MCP 插件功能受账号计划、工作区权限和产品开放范围影响。
尤其是新增、修改和删除数据的写入工具,并不保证所有账号都拥有相同的访问权限。
因此,课程学员需要先确认自己的 ChatGPT 账号是否支持自定义 MCP 插件及相应写入操作。官方的功能说明会持续更新。
问题五:ChatGPT 是否可以直接访问 health.db?
不可以。
在本章架构中,ChatGPT 并没有获得读取 Mac 文件系统的通用权限。
它只能通过我们明确注册、授权并暴露的 MCP 工具来查询或修改数据库。
这也是 MCP 工具接口的重要价值:我们可以控制 AI 能够执行哪些操作。
5.11 本章总结
这一章,我们终于完成了课程中最关键的一次转变。
之前的 Health Tracker 只是一个通过 MCP Inspector 操作的 Python 数据管理程序。
现在,我们将它连接到了 ChatGPT。
整个工作流程是:

回顾整个过程,我们没有在 Python 中调用大语言模型 API,也没有自己训练食物识别模型。
我们只是开发了一套 MCP 工具,并通过 Tunnel 将它们提供给 ChatGPT。
ChatGPT 利用已有的语言理解和推理能力,决定调用什么工具、生成什么参数。
Python MCP Server 则负责执行具体操作。
这正是本课程希望学员掌握的核心能力:不是把 AI 接入自己的 Python 程序,而是让 AI 能够调用自己开发的 Python 工具。
下一章预告:完善 Health Tracker 的数据管理
现在,我们已经能够通过 ChatGPT 新增和查询健康记录。
但真实使用中,还会遇到新的问题。
例如:
昨天中午的米饭不是 200 克,而是 100 克,帮我修改。
或者:
刚才那条香蕉记录是测试数据,帮我删除。
这就涉及记录定位、修改、删除和权限控制。
下一章,我们将继续开发:
find_food_records:查找符合条件的饮食记录。update_food:修改指定饮食记录。delete_food:删除指定饮食记录。- 工具参数校验与误操作防护。
- 修改 MCP 工具后,如何同步更新 ChatGPT 插件。
完成后,Health Tracker 将具备更加完整的增删改查能力,可以开始用于日常健康记录。
想系统学习这门课程?
你已阅读完本文。需要更完整的知识体系,可以继续了解「Vibe Coding 实战课程」。
查看课程详情